/*
 * name: pjax.js
 * version: v0.0.1
 * update: build
 * date: 2015-09-28
 */
define('pjax', function(require, exports, module) {
	/*
	 * pjax(ajax + history.pushState) for jquery
	 * 
	 * by welefen
	 */
	var $ = require('jquery'),
		Util = {
			support: {
				pjax: window.history && window.history.pushState && window.history.replaceState && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/),
				storage: !!window.localStorage
			},
			toInt: function(obj) {
				return parseInt(obj);
			},
			stack: {},
			getTime: function() {
				return new Date * 1;
			},
			getRealUrl: function(url) {
				url = (url || '').replace(/\#.*?$/, '');
				url = url.replace('?pjax=true&', '?').replace('?pjax=true', '').replace('&pjax=true', '');
				return url;
			},
			getUrlHash: function(url) {
				return url.replace(/^[^\#]*(?:\#(.*?))?$/, '$1');
			},
			getLocalKey: function(src) {
				var s = 'pjax_' + encodeURIComponent(src);
				return {
					data: s + '_data',
					time: s + '_time',
					title: s + '_title'
				};
			},
			removeAllCache: function() {
				if (!Util.support.storage)
					return;
				for (var name in localStorage) {
					if ((name.split('_') || [''])[0] === 'pjax') {
						delete localStorage[name];
					}
				}
			},
			getCache: function(src, time, flag) {
				var item, vkey, tkey, tval;
				time = Util.toInt(time);
				if (src in Util.stack) {
					item = Util.stack[src], ctime = Util.getTime();
					if ((item.time + time * 1000) > ctime) {
						return item;
					} else {
						delete Util.stack[src];
					}
				} else if (flag && Util.support.storage) {
					var l = Util.getLocalKey(src);
					vkey = l.data;
					tkey = l.time;
					item = localStorage.getItem(vkey);
					if (item) {
						tval = Util.toInt(localStorage.getItem(tkey));
						if ((tval + time * 1000) > Util.getTime()) {
							return {
								data: item,
								title: localStorage.getItem(l.title)
							};
						} else {
							localStorage.removeItem(vkey);
							localStorage.removeItem(tkey);
							localStorage.removeItem(l.title);
						}
					}
				}
				return null;
			},
			setCache: function(src, data, title, flag) {
				var time = Util.getTime(),
					key;
				Util.stack[src] = {
					data: data,
					title: title,
					time: time
				};
				if (flag && Util.support.storage) {
					key = Util.getLocalKey(src);
					localStorage.setItem(key.data, data);
					localStorage.setItem(key.time, time);
					localStorage.setItem(key.title, title);
				}
			},
			removeCache: function(src) {
				src = Util.getRealUrl(src || location.href);
				delete Util.stack[src];
				if (Util.support.storage) {
					var key = Util.getLocalKey(src);
					localStorage.removeItem(key.data);
					localStorage.removeItem(key.time);
					localStorage.removeItem(key.title);
				}
			}
		};
	var pjax = function(options) {
		options = $.extend({
			selector: '',
			container: '',
			callback: function() {},
			fitler: function() {}
		}, options);
		if (!options.container || !options.selector) {
			throw new Error('selector & container options must be set');
		}
		$('body').delegate(options.selector, 'click', function(event) {
			if (event.which > 1 || event.metaKey) {
				return true;
			}
			var $this = $(this),
				href = $this.attr('href');
			if (typeof options.filter === 'function') {
				if (options.filter.call(this, href, this) === true) {
					return true;
				}
			}
			if (href === location.href) {
				return true;
			}
			if (Util.getRealUrl(href) == Util.getRealUrl(location.href)) {
				var hash = Util.getUrlHash(href);
				if (hash) {
					location.hash = hash;
					options.callback && options.callback.call(this, {
						type: 'hash'
					});
				}
				return true;
			}
			event.preventDefault();
			options = $.extend(true, options, {
				url: href,
				element: this,
				title: '',
				push: true
			});
			pjax.request(options);
		});
	};
	pjax.xhr = null;
	pjax.options = {};
	pjax.state = {};

	// 默认选项
	pjax.defaultOptions = {
		timeout: 2000,
		element: null,
		cache: 24 * 3600, // 缓存时间, 0为不缓存, 单位为秒
		storage: true,
		url: '', // 链接地址
		push: true, // true is push, false is replace, null for do nothing
		show: '', // 动画
		title: '', // 标题
		titleSuffix: '', // 标题后缀
		type: 'GET',
		data: {
			pjax: true
		},
		dataType: 'html',
		callback: null,
		beforeSend: function(xhr) {
			$(pjax.options.container).trigger('pjax.start', [xhr, pjax.options]);
			xhr && xhr.setRequestHeader('X-PJAX', true);
		},
		error: function() {
			pjax.options.callback && pjax.options.callback.call(pjax.options.element, {
				type: 'error'
			});
			location.href = pjax.options.url;
		},
		complete: function(xhr) {
			$(pjax.options.container).trigger('pjax.end', [xhr, pjax.options]);
		}
	};
	pjax.showFx = {
		"_default": function(data, callback, isCached) {
			this.html(data);
			callback && callback.call(this, data, isCached);
		},
		fade: function(data, callback, isCached) {
			var $this = this;
			if (isCached) {
				$this.html(data);
				callback && callback.call($this, data, isCached);
			} else {
				this.fadeOut(500, function() {
					$this.html(data).fadeIn(500, function() {
						callback && callback.call($this, data, isCached);
					});
				});
			}
		}
	}
	pjax.showFn = function(showType, container, data, fn, isCached) {
		var fx = null;
		if (typeof showType === 'function') {
			fx = showType;
		} else {
			if (!(showType in pjax.showFx)) {
				showType = "_default";
			}
			fx = pjax.showFx[showType];
		}
		fx && fx.call(container, data, function() {
			var hash = location.hash;
			if (hash != '') {
				location.href = hash;
				if (/Firefox/.test(navigator.userAgent)) {
					history.replaceState($.extend({}, pjax.state, {
						url: null
					}), document.title);
				}
			} else {
				window.scrollTo(0, 0);
			}
			fn && fn.call(this, data, isCached);
		}, isCached);
	}
	pjax.success = function(data, isCached) {
		if (isCached !== true) {
			isCached = false;
		}
		if ((data || '').indexOf('<html') != -1) {
			pjax.options.callback && pjax.options.callback.call(pjax.options.element, {
				type: 'error'
			});
			location.href = pjax.options.url;
			return false;
		}
		var title = pjax.options.title || "",
			el;
		if (pjax.options.element) {
			el = $(pjax.options.element);
			title = el.attr('title') || el.text();
		}
		var matches = data.match(/<title>(.*?)<\/title>/);
		if (matches) {
			title = matches[1];
		}
		if (title) {
			if (title.indexOf(pjax.options.titleSuffix) == -1) {
				title += pjax.options.titleSuffix;
			}
		}
		document.title = title;
		pjax.state = {
			container: pjax.options.container,
			timeout: pjax.options.timeout,
			cache: pjax.options.cache,
			storage: pjax.options.storage,
			show: pjax.options.show,
			title: title,
			url: pjax.options.oldUrl
		};
		var query = $.param(pjax.options.data);
		if (query != "") {
			pjax.state.url = pjax.options.url + (/\?/.test(pjax.options.url) ? "&" : "?") + query;
		}
		if (pjax.options.push) {
			if (!pjax.active) {
				history.replaceState($.extend({}, pjax.state, {
					url: null
				}), document.title);
				pjax.active = true;
			}
			history.pushState(pjax.state, document.title, pjax.options.oldUrl);
		} else if (pjax.options.push === false) {
			history.replaceState(pjax.state, document.title, pjax.options.oldUrl);
		}
		pjax.options.showFn && pjax.options.showFn(data, function() {
			pjax.options.callback && pjax.options.callback.call(pjax.options.element, {
				type: isCached ? 'cache' : 'success'
			});
		}, isCached);
		if (pjax.options.cache && !isCached) {
			Util.setCache(pjax.options.url, data, title, pjax.options.storage);
		}
	};
	pjax.request = function(options) {
		options = $.extend(true, pjax.defaultOptions, options);
		var cache, container = $(options.container);
		options.oldUrl = options.url;
		options.url = Util.getRealUrl(options.url);
		if ($(options.element).length) {
			cache = Util.toInt($(options.element).attr('data-pjax-cache'));
			if (cache) {
				options.cache = cache;
			}
		}
		if (options.cache === true) {
			options.cache = 24 * 3600;
		}
		options.cache = Util.toInt(options.cache);
		if (options.cache === 0) {
			Util.removeAllCache();
		}
		if (!options.showFn) {
			options.showFn = function(data, fn, isCached) {
				pjax.showFn(options.show, container, data, fn, isCached);
			};
		}
		pjax.options = options;
		pjax.options.success = pjax.success;
		if (options.cache && (cache = Util.getCache(options.url, options.cache, options.storage))) {
			options.beforeSend();
			options.title = cache.title;
			pjax.success(cache.data, true);
			options.complete();
			return true;
		}
		if (pjax.xhr && pjax.xhr.readyState < 4) {
			pjax.xhr.onreadystatechange = $.noop;
			pjax.xhr.abort();
		}
		pjax.xhr = $.ajax(pjax.options);
	};
	var popped = ('state' in window.history),
		initialURL = location.href;
	$(window).bind('popstate', function(event) {
		var initialPop = !popped && location.href == initialURL;
		popped = true;
		if (initialPop) return;
		var state = event.state;
		if (state && state.container) {
			if ($(state.container).length) {
				var data = {
					url: state.url,
					container: state.container,
					push: null,
					timeout: state.timeout,
					cache: state.cache,
					storage: state.storage,
					title: state.title,
					element: null
				};
				pjax.request(data);
			} else {
				window.location = location.href;
			}
		}
	});
	if (!Util.support.pjax) {
		pjax = function() {
			return true;
		};
		pjax.request = function(options) {
			if (options && options.url) {
				location.href = options.url;
			}
		};
	}
	$.pjax = pjax;
	$.pjax.util = Util;
	if ($.inArray('state', $.event.props) < 0) {
		$.event.props.push('state')
	}
})